/*
 Copyright © 2020 The OpenEBS Authors

 This file was originally authored by Rancher Labs
 under Apache License 2018.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

package sync

import (
	"fmt"

	"github.com/openebs/jiva/controller/rest"
	"github.com/openebs/jiva/replica"
	replicaClient "github.com/openebs/jiva/replica/client"
	"github.com/openebs/jiva/util"
	"github.com/sirupsen/logrus"
)

func (t *Task) CreateBackup(snapshot, dest string) (string, error) {
	var replica *rest.Replica

	volume, err := t.client.GetVolume()
	if err != nil {
		return "", err
	}

	replicas, err := t.client.ListReplicas()
	if err != nil {
		return "", err
	}

	for _, r := range replicas {
		if r.Mode == "RW" {
			replica = &r
			break
		}
	}

	if replica == nil {
		return "", fmt.Errorf("Cannot find a suitable replica for backup")
	}

	backup, err := t.createBackup(replica, snapshot, dest, volume.Name)
	if err != nil {
		return "", err
	}
	return backup, nil
}

func (t *Task) createBackup(replicaInController *rest.Replica, snapshot, dest, volumeName string) (string, error) {
	if replicaInController.Mode != "RW" {
		return "", fmt.Errorf("Can only create backup from replica in mode RW, got %s", replicaInController.Mode)
	}

	repClient, err := replicaClient.NewReplicaClient(replicaInController.Address)
	if err != nil {
		return "", err
	}

	replica, err := repClient.GetReplica()
	if err != nil {
		return "", err
	}

	snapshotName, index := getNameAndIndex(replica.Chain, snapshot)
	switch {
	case index < 0:
		return "", fmt.Errorf("Snapshot %s not found on replica %s", snapshot, replicaInController.Address)
	case index == 0:
		return "", fmt.Errorf("Can not backup the head disk in the chain")
	}

	logrus.Infof("Backing up %s on %s, to %s", snapshotName, replicaInController.Address, dest)

	backup, err := repClient.CreateBackup(snapshotName, dest, volumeName)
	if err != nil {
		logrus.Errorf("Failed backing up %s on %s to %s", snapshotName, replicaInController.Address, dest)
		return "", err
	}
	return backup, nil
}

func (t *Task) RmBackup(backup string) error {
	var replica *rest.Replica

	replicas, err := t.client.ListReplicas()
	if err != nil {
		return err
	}

	for _, r := range replicas {
		if r.Mode == "RW" {
			replica = &r
			break
		}
	}

	if replica == nil {
		return fmt.Errorf("Cannot find a suitable replica for remove backup")
	}

	return t.rmBackup(replica, backup)
}

func (t *Task) rmBackup(replicaInController *rest.Replica, backup string) error {
	if replicaInController.Mode != "RW" {
		return fmt.Errorf("Can only remove backup from replica in mode RW, got %s", replicaInController.Mode)
	}

	repClient, err := replicaClient.NewReplicaClient(replicaInController.Address)
	if err != nil {
		return err
	}

	logrus.Infof("Removing backup %s on %s", backup, replicaInController.Address)

	if err := repClient.RmBackup(backup); err != nil {
		logrus.Errorf("Failed removing backup %s on %s", backup, replicaInController.Address)
		return err
	}
	return nil
}

func (t *Task) RestoreBackup(backup string) error {
	replicas, err := t.client.ListReplicas()
	if err != nil {
		return err
	}

	for _, r := range replicas {
		if ok, err := t.isRebuilding(&r); err != nil {
			return err
		} else if ok {
			return fmt.Errorf("Can not restore from backup because %s is rebuilding", r.Address)
		}
	}

	// generate new snapshot and metafile as base for new volume
	snapshotID := util.UUID()
	snapshotFile := replica.GenerateSnapshotDiskName(snapshotID)
	for _, replica := range replicas {
		if err := t.restoreBackup(&replica, backup, snapshotFile); err != nil {
			return err
		}
	}

	// call to controller to revert to sfile
	if err := t.client.RevertSnapshot(snapshotID); err != nil {
		return fmt.Errorf("Fail to revert to snapshot %v", snapshotID)
	}
	return nil
}

func (t *Task) restoreBackup(replicaInController *rest.Replica, backup string, snapshotFile string) error {
	if replicaInController.Mode != "RW" {
		return fmt.Errorf("Can only restore backup from replica in mode RW, got %s", replicaInController.Mode)
	}

	repClient, err := replicaClient.NewReplicaClient(replicaInController.Address)
	if err != nil {
		return err
	}

	logrus.Infof("Restoring backup %s on %s", backup, replicaInController.Address)

	if err := repClient.RestoreBackup(backup, snapshotFile); err != nil {
		logrus.Errorf("Failed restoring backup %s on %s", backup, replicaInController.Address)
		return err
	}
	return nil
}

func (t *Task) InspectBackup(backup string) (string, error) {
	var replica *rest.Replica

	replicas, err := t.client.ListReplicas()
	if err != nil {
		return "", err
	}

	for _, r := range replicas {
		if r.Mode == "RW" {
			replica = &r
			break
		}
	}

	if replica == nil {
		return "", fmt.Errorf("Cannot find a suitable replica for inspect backup")
	}

	result, err := t.inspectBackup(replica, backup)
	if err != nil {
		return "", err
	}
	return result, nil
}

func (t *Task) inspectBackup(replicaInController *rest.Replica, backup string) (string, error) {
	if replicaInController.Mode != "RW" {
		return "", fmt.Errorf("Can only inspect backup from replica in mode RW, got %s", replicaInController.Mode)
	}

	repClient, err := replicaClient.NewReplicaClient(replicaInController.Address)
	if err != nil {
		return "", err
	}

	result, err := repClient.InspectBackup(backup)
	if err != nil {
		logrus.Errorf("Failed inspect backup %s on %s", backup, replicaInController.Address)
		return "", err
	}
	return result, nil
}

func (t *Task) ListBackup(destURL string) (string, error) {
	var replica *rest.Replica

	volume, err := t.client.GetVolume()
	if err != nil {
		return "", err
	}

	replicas, err := t.client.ListReplicas()
	if err != nil {
		return "", err
	}

	for _, r := range replicas {
		if r.Mode == "RW" {
			replica = &r
			break
		}
	}

	if replica == nil {
		return "", fmt.Errorf("Cannot find a suitable replica for list backup")
	}

	result, err := t.listBackup(replica, destURL, volume.Name)
	if err != nil {
		return "", err
	}
	return result, nil
}

func (t *Task) listBackup(replicaInController *rest.Replica, destURL, volume string) (string, error) {
	if replicaInController.Mode != "RW" {
		return "", fmt.Errorf("Can only list backup from replica in mode RW, got %s", replicaInController.Mode)
	}

	repClient, err := replicaClient.NewReplicaClient(replicaInController.Address)
	if err != nil {
		return "", err
	}

	result, err := repClient.ListBackup(destURL, volume)
	if err != nil {
		logrus.Errorf("Failed to list backup %s with volume %s on %s",
			destURL, volume, replicaInController.Address)
		return "", err
	}
	return result, nil
}
